package io.gitee.mingbaobaba.security.oauth2.service;

import io.gitee.mingbaobaba.security.core.constants.ErrorCodeConstant;
import io.gitee.mingbaobaba.security.core.constants.SecurityConstant;
import io.gitee.mingbaobaba.security.core.context.SecurityContext;
import io.gitee.mingbaobaba.security.core.domain.SecurityLoginParams;
import io.gitee.mingbaobaba.security.core.domain.SecuritySession;
import io.gitee.mingbaobaba.security.core.domain.SecurityToken;
import io.gitee.mingbaobaba.security.core.domain.SecurityUserDetails;
import io.gitee.mingbaobaba.security.core.exception.SecurityBusinessException;
import io.gitee.mingbaobaba.security.core.factory.SecurityFactory;
import io.gitee.mingbaobaba.security.core.request.SecurityRequest;
import io.gitee.mingbaobaba.security.core.service.SecurityService;
import io.gitee.mingbaobaba.security.core.service.SecurityUserDetailsService;
import io.gitee.mingbaobaba.security.core.utils.CommonUtil;
import io.gitee.mingbaobaba.security.core.utils.SecurityUtil;
import io.gitee.mingbaobaba.security.oauth2.SecurityOauth2Manager;
import io.gitee.mingbaobaba.security.oauth2.constants.SecurityOauth2CommonConstant;
import io.gitee.mingbaobaba.security.oauth2.constants.SecurityOauth2ErrorCodeConstant;
import io.gitee.mingbaobaba.security.oauth2.constants.SecurityOauth2ParamConstant;
import io.gitee.mingbaobaba.security.oauth2.domain.*;
import io.gitee.mingbaobaba.security.oauth2.domain.model.AuthorizeModel;
import io.gitee.mingbaobaba.security.oauth2.enums.GrantType;
import io.gitee.mingbaobaba.security.oauth2.exception.SecurityOauth2Exception;
import io.gitee.mingbaobaba.security.oauth2.repository.SecurityOauth2ApplicationRepository;
import io.gitee.mingbaobaba.security.oauth2.repository.SecurityOauth2Repository;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;

import java.util.Base64;
import java.util.Date;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Stream;

/**
 * <p>授权服务实现</p>
 *
 * @author yingsheng.ye
 * @version 1.0.0
 * @since 2023/9/9 21:54
 */
public class SecurityOauth2ServiceImpl implements SecurityOauth2Service {

    /**
     * 授权码认证
     *
     * @return 授权码
     */
    @Override
    public AuthorizeModel buildAuthorize() {
        AuthorizeModel authorizeModel = new AuthorizeModel();
        SecurityRequest securityRequest = SecurityFactory.getSecurityRequest.get();

        //client_id
        String clientId = securityRequest.getParameterNonNull(SecurityOauth2ParamConstant.CLIENT_ID);

        SecurityOauth2ApplicationRepository applicationRepository = SecurityOauth2Manager.getSecurityOauth2ApplicationRepository();
        //查询应用信息
        SecurityOauth2Application securityOauth2Application = applicationRepository.getOauth2ApplicationByClientId(clientId);

        //验证客户端
        if (Objects.isNull(securityOauth2Application)) {
            throw new SecurityOauth2Exception(SecurityOauth2ErrorCodeConstant.OAUTH2_CODE_CLIENT_NO_AUTH, "客户端未授权");
        }
        //response_type
        String responseType = validResponseType(securityRequest);

        //验证授权类型
        GrantType grantType = getGrantTypeByResponseType(responseType);
        validGrantType(grantType, securityOauth2Application);

        //redirect_uri
        String redirectUri = validRedirectUri(securityRequest, securityOauth2Application);
        //scope
        String scope = validScope(securityRequest, securityOauth2Application);
        //状态 - 透传
        String state = securityRequest.getParameter(SecurityOauth2ParamConstant.STATE);

        //构建授权客户端信息
        SecurityOauth2Client securityClientModel = SecurityOauth2Client.builder()
                .clientId(securityOauth2Application.getClientId())
                .clientName(StringUtils.defaultString(securityOauth2Application.getClientName(), securityOauth2Application.getClientId()))
                .clientSecret(securityOauth2Application.getClientSecret())
                .responseType(responseType)
                .scope(scope)
                .redirectUri(redirectUri)
                .state(state)
                .autoAgreeAuthorization(securityOauth2Application.getAutoAgreeAuthorization())
                .build();

        if (Boolean.TRUE.equals(SecurityUtil.isLogin())) {
            //已登录
            String tokenValue = SecurityUtil.getCurrentTokenValue();
            //存储token
            securityClientModel.setToken(tokenValue);
            //授权码模式
            if (GrantType.AUTHORIZATION_CODE.equals(grantType)) {
                //生成授权code
                String authorizationCode = UUID.randomUUID().toString().replaceAll("-", "");
                //保存授权码
                SecurityOauth2Repository securityOauth2Repository = SecurityOauth2Manager.getSecurityOauth2Repository();
                //保存授权码相关信息 有效期为10分钟
                securityOauth2Repository.saveAuthorizationCode(authorizationCode, securityClientModel, 60L * 10);
                authorizeModel.setCode(authorizationCode);
            }
            //隐私模式
            if (GrantType.IMPLICIT.equals(grantType)) {
                SecurityToken securityToken = SecurityUtil.getCurrentToken();
                SecurityOauth2AccessToken oauth2AccessToken = new SecurityOauth2AccessToken();
                oauth2AccessToken.setAccessToken(securityToken.getToken());
                oauth2AccessToken.setIssuedAt(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
                oauth2AccessToken.setExpiresIn(securityToken.getTimeout());
                authorizeModel.setSecurityOauth2AccessToken(oauth2AccessToken);
            }
            authorizeModel.setAuthorizeSuccess(true);
        }
        authorizeModel.setSecurityOauth2Client(securityClientModel);
        return authorizeModel;
    }

    @Override
    public SecurityOauth2Application buildSecurityOauth2Application() {
        SecurityRequest securityRequest = SecurityFactory.getSecurityRequest.get();
        SecurityOauth2ApplicationRepository applicationRepository = SecurityOauth2Manager.getSecurityOauth2ApplicationRepository();

        //从header中解析
        String basic = securityRequest.getHeader(SecurityOauth2CommonConstant.HEADER_NAME_BASIC);
        //客户端ID
        String clientId = SecurityOauth2CommonConstant.EMPTY_STR;
        //客户端密钥
        String clientSecret = SecurityOauth2CommonConstant.EMPTY_STR;
        if (StringUtils.isNoneBlank(basic)) {
            //解码
            String basicStr = new String(Base64.getDecoder().decode(basic));
            String[] basicArr = basicStr.split(SecurityConstant.SPLIT_COMMA);
            if (basicArr.length == 2) {
                clientId = Objects.isNull(basicArr[0]) ? SecurityOauth2CommonConstant.EMPTY_STR : basicArr[0];
                clientSecret = Objects.isNull(basicArr[1]) ? SecurityOauth2CommonConstant.EMPTY_STR : basicArr[1];
            }
        } else {
            clientId = securityRequest.getParameterNonNull(SecurityOauth2ParamConstant.CLIENT_ID);
            clientSecret = securityRequest.getParameterNonNull(SecurityOauth2ParamConstant.CLIENT_SECRET);
        }

        //查询应用信息
        SecurityOauth2Application securityOauth2Application = applicationRepository.getOauth2ApplicationByClientId(clientId);

        //验证客户端
        if (Objects.isNull(securityOauth2Application)) {
            throw new SecurityOauth2Exception(SecurityOauth2ErrorCodeConstant.OAUTH2_CODE_CLIENT_NO_AUTH, "客户端未授权");
        }

        //验证密钥
        if (!clientSecret.equals(securityOauth2Application.getClientSecret())) {
            throw new SecurityOauth2Exception(SecurityOauth2ErrorCodeConstant.OAUTH2_CODE_CLIENT_SECRET_ERR, "客户端密钥错误");
        }
        return securityOauth2Application;
    }

    @Override
    public SecurityOauth2Client buildAuthorizationModel(GrantType grantType) {
        SecurityRequest securityRequest = SecurityFactory.getSecurityRequest.get();

        //应用信息
        SecurityOauth2Application securityOauth2Application = buildSecurityOauth2Application();

        //授权范围
        String scope = validScope(securityRequest, securityOauth2Application);

        //返回类型
        String responseType = validResponseType(securityRequest);

        //验证授权类型
        validGrantType(grantType, securityOauth2Application);

        //状态 - 透传
        String state = securityRequest.getParameter(SecurityOauth2ParamConstant.STATE);

        //回调验证
        String redirectUri = null;
        if (GrantType.AUTHORIZATION_CODE.equals(grantType)) {
            redirectUri = validRedirectUri(securityRequest, securityOauth2Application);
        }

        return SecurityOauth2Client.builder()
                .clientId(securityOauth2Application.getClientId())
                .clientName(StringUtils.defaultString(securityOauth2Application.getClientName(), securityOauth2Application.getClientId()))
                .clientSecret(securityOauth2Application.getClientSecret())
                .responseType(responseType)
                .scope(scope)
                .redirectUri(redirectUri)
                .state(state)
                .build();
    }

    /**
     * 返回类型获取授权类型
     *
     * @param responseType responseType
     * @return 返回类型值
     */
    private GrantType getGrantTypeByResponseType(String responseType) {
        if (SecurityOauth2CommonConstant.RESPONSE_TYPE_CODE.equals(responseType)) {
            return GrantType.AUTHORIZATION_CODE;
        }
        if (SecurityOauth2CommonConstant.RESPONSE_TYPE_TOKEN.equals(responseType)) {
            return GrantType.IMPLICIT;
        }
        return null;
    }


    /**
     * 验证授权访问
     *
     * @param securityRequest securityRequest
     * @param application     SecurityOauth2Application
     * @return 授权范围值
     */
    private static String validScope(SecurityRequest securityRequest, SecurityOauth2Application application) {
        String scope = securityRequest.getParameterNonNull(SecurityOauth2ParamConstant.SCOPE);
        if (Objects.isNull(application.getScope()) || Stream.of(application.getScope()
                .split(SecurityConstant.SPLIT_COMMA)).noneMatch(str -> str.equals(scope))) {
            throw new SecurityOauth2Exception(SecurityOauth2ErrorCodeConstant.OAUTH2_CODE_CLIENT_SCOPE_ERR, "客户端授权范围错误");
        }
        return scope;
    }

    /**
     * 验证返回类型
     *
     * @param securityRequest SecurityRequest
     * @return 返回类型值
     */
    private static String validResponseType(SecurityRequest securityRequest) {
        String responseType = securityRequest.getParameter(SecurityOauth2ParamConstant.RESPONSE_TYPE);
        if (StringUtils.isBlank(responseType)) {
            responseType = (String) securityRequest.getAttribute(SecurityOauth2ParamConstant.RESPONSE_TYPE);
        }
        if (!SecurityOauth2CommonConstant.RESPONSE_TYPE_CODE.equals(responseType) &&
                !SecurityOauth2CommonConstant.RESPONSE_TYPE_TOKEN.equals(responseType)
        ) {
            throw new SecurityOauth2Exception(SecurityOauth2ErrorCodeConstant.OAUTH2_CODE_CLIENT_RESPONSE_TYPE_ERR,
                    "客户端请求返回类型错误");
        }
        return responseType;
    }

    /**
     * 验证授权类型
     *
     * @param grantType   授权类型
     * @param application SecurityOauth2Application
     */
    private static void validGrantType(GrantType grantType, SecurityOauth2Application application) {
        if (Objects.isNull(application.getGrantType()) ||
                Stream.of(application.getGrantType().split(SecurityConstant.SPLIT_COMMA))
                        .noneMatch(str -> str.equals(grantType.getCode()))) {
            throw new SecurityOauth2Exception(SecurityOauth2ErrorCodeConstant.OAUTH2_CODE_CLIENT_RESPONSE_TYPE_ERR,
                    "不支持此授权类型");
        }

    }

    /**
     * 验证回调地址
     *
     * @param securityRequest SecurityRequest
     * @param application     SecurityOauth2Application
     * @return 回调地址
     */
    private static String validRedirectUri(SecurityRequest securityRequest, SecurityOauth2Application application) {
        String redirectUri = securityRequest.getParameterNonNull(SecurityOauth2ParamConstant.REDIRECT_URI);
        if (Objects.isNull(application.getRedirectUri()) ||
                Stream.of(application.getRedirectUri().split(SecurityConstant.SPLIT_COMMA))
                        .noneMatch(str -> str.equals(redirectUri))) {
            throw new SecurityOauth2Exception(SecurityOauth2ErrorCodeConstant.OAUTH2_CODE_CLIENT_REDIRECT_URI_ERR, "客户端回调地址未授权");
        }
        return redirectUri;
    }


    @Override
    public String buildAuthorizationCodeUri(String authorizationCode) {
        SecurityOauth2Repository securityOauth2Repository = SecurityOauth2Manager.getSecurityOauth2Repository();
        SecurityOauth2Client securityClientModel = securityOauth2Repository.getClientModelByAuthorizationCode(authorizationCode);
        if (null == securityClientModel) {
            throw new SecurityOauth2Exception(SecurityOauth2ErrorCodeConstant.OAUTH2_CODE_INVALID_AUTHORIZATION_CODE, "授权码已失效");
        }
        String redirectUri = securityClientModel.getRedirectUri();
        //拼装地址
        return redirectUri + (redirectUri.contains("?") ? "&" : "?") +
                "code=" + authorizationCode +
                "&state=" + (StringUtils.isNoneBlank(securityClientModel.getState()) ? securityClientModel.getState() : "");
    }

    @Override
    public SecurityOauth2AccessToken getAccessTokenByAuthorizationCode(String authorizationCode) {
        SecurityOauth2Repository securityOauth2Repository = SecurityOauth2Manager.getSecurityOauth2Repository();
        SecurityOauth2Client clientModel = securityOauth2Repository.getClientModelByAuthorizationCode(authorizationCode);
        if (Objects.isNull(clientModel)) {
            throw new SecurityOauth2Exception(SecurityOauth2ErrorCodeConstant
                    .OAUTH2_CODE_INVALID_AUTHORIZATION_CODE, "无效的授权码");
        }
        SecurityOauth2AccessToken securityOauth2AccessToken = grantAuthorizationLogin(clientModel, GrantType.AUTHORIZATION_CODE);
        if (Objects.nonNull(securityOauth2AccessToken)) {
            //删除授权码信息
            securityOauth2Repository.removeAuthorizationCode(authorizationCode);
        }
        return securityOauth2AccessToken;
    }

    @Override
    public void revokeAuthorization(String authorizationCode) {
        SecurityOauth2Repository securityOauth2Repository = SecurityOauth2Manager.getSecurityOauth2Repository();
        securityOauth2Repository.removeAuthorizationCode(authorizationCode);
    }

    @Override
    public SecurityOauth2AccessToken grantAuthorizationLogin(SecurityOauth2Client clientModel, GrantType grantType) {
        SecurityRequest securityRequest = SecurityFactory.getSecurityRequest.get();
        SecurityContext securityContext = SecurityFactory.getSecurityContext.get();
        SecurityLoginParams loginParams = new SecurityLoginParams();
        loginParams.setTokenAttribute(SecurityOauth2ParamConstant.CLIENT_ID, clientModel.getClientId())
                .setTokenAttribute(SecurityOauth2ParamConstant.GRANT_TYPE, grantType.getCode())
                .setTokenAttribute(SecurityOauth2ParamConstant.SCOPE, clientModel.getScope())
                .setTokenAttribute(SecurityOauth2ParamConstant.STATE, clientModel.getState())
                .setTimeout(SecurityOauth2Manager.getConfig().getRefreshTokenTimeout())
                .setActivityTimeout(SecurityOauth2Manager.getConfig().getAccessTokenTimeout());
        SecurityOauth2Repository securityOauth2Repository = SecurityOauth2Manager.getSecurityOauth2Repository();
        SecuritySession session;
        //用户token不为空
        if (StringUtils.isNotBlank(clientModel.getToken())) {
            session = SecurityUtil.securityService.get().getSecuritySessionByToken(clientModel.getToken());
        } else {
            if (Boolean.FALSE.equals(SecurityUtil.isLogin())) {
                if (GrantType.CLIENT_CREDENTIALS.getCode().equals(grantType.getCode())) {
                    //客户端模式，没有用户名和密码,直接登录，适合接口调用，无用户
                    //指定一个不存在的用户登录Id
                    String noLoginId = "-1";
                    SecurityUtil.doLogin(noLoginId, loginParams);
                } else {
                    SecurityUserDetailsService userDetailsService = SecurityFactory.getSecurityUserDetailsService.get();
                    if (StringUtils.isBlank(clientModel.getUsername())) {
                        //用户名
                        String username = securityRequest.getParameterNonNull(SecurityOauth2ParamConstant.USERNAME);
                        clientModel.setUsername(username);
                    }
                    //查询用户信息
                    SecurityUserDetails userDetails = userDetailsService.findSecurityUserDetailsByUsername(clientModel.getUsername());
                    if (Objects.isNull(userDetails) || StringUtils.isBlank(userDetails.getLoginId())) {
                        throw new SecurityOauth2Exception(ErrorCodeConstant.CODE_NO_EXIST_USER, "获取登录用户不存在");
                    }
                    if (!SecurityFactory.getSecurityUserDetailsService.get().preHandle(clientModel.getUsername(),
                            loginParams, securityContext)) {
                        throw new SecurityBusinessException(ErrorCodeConstant.CODE_DISABLED_LOGIN, "登录操作被限制");
                    }
                    //密码
                    if (StringUtils.isBlank(clientModel.getPassword())) {
                        String password = securityRequest.getParameterNonNull(SecurityOauth2ParamConstant.PASSWORD);
                        clientModel.setPassword(password);
                    }
                    if (StringUtils.isBlank(userDetails.getPassword()) ||
                            !userDetails.getPassword().equals(userDetailsService.passwordPolicy(clientModel.getPassword(), userDetails, securityContext))) {
                        throw new SecurityOauth2Exception(ErrorCodeConstant.CODE_PASSWORD_ERR, "密码错误");
                    }
                    SecurityUtil.doLogin(userDetails.getLoginId(), loginParams);
                    userDetailsService.afterCompletion();
                }
            }
            session = SecurityUtil.getCurrentSecuritySession();
        }
        SecurityToken securityToken = session.getCurrentSecurityToken();
        //构建accessToken信息
        SecurityOauth2AccessToken oauth2AccessToken = new SecurityOauth2AccessToken();
        oauth2AccessToken.setAccessToken(securityToken.getToken());
        oauth2AccessToken.setExpiresIn(SecurityConstant.NON_EXPIRING.equals(securityToken.getActivityTimeout())
                ? securityToken.getTimeout() : securityToken.getActivityTimeout());
        oauth2AccessToken.setIssuedAt(session.getCreateTime());

        //只有授权码和密码模式支持刷新token
        if (GrantType.AUTHORIZATION_CODE.getCode().equals(grantType.getCode())
                || GrantType.PASSWORD.getCode().equals(grantType.getCode())
        ) {
            oauth2AccessToken.setRefreshToken(CommonUtil.generateToken.get());
            oauth2AccessToken.setRefreshExpiresIn(securityToken.getTimeout());
            final SecurityOauth2Details securityOauth2Details = getSecurityOauth2Details(clientModel, session, oauth2AccessToken);
            //存储详情
            if (!securityOauth2Repository.saveOauth2TokenDetails(securityOauth2Details, oauth2AccessToken.getRefreshExpiresIn())) {
                throw new SecurityOauth2Exception(SecurityOauth2ErrorCodeConstant.OAUTH2_CODE_SAVE_ACCESS_CODE_ERR, "保存Oauth2AccessToken异常");
            }
        }
        return oauth2AccessToken;
    }

    private SecurityOauth2Details getSecurityOauth2Details(SecurityOauth2Client clientModel, SecuritySession session, SecurityOauth2AccessToken oauth2AccessToken) {
        SecurityOauth2Details securityOauth2Details = new SecurityOauth2Details();
        securityOauth2Details.setClientId(clientModel.getClientId());
        securityOauth2Details.setScope(clientModel.getScope());
        securityOauth2Details.setLoginId(session.getLoginId());
        securityOauth2Details.setUsername(clientModel.getUsername());
        securityOauth2Details.setAccessToken(oauth2AccessToken.getAccessToken());
        securityOauth2Details.setRefreshToken(oauth2AccessToken.getRefreshToken());
        securityOauth2Details.setRefreshExpiresIn(oauth2AccessToken.getRefreshExpiresIn());
        return securityOauth2Details;
    }


    @Override
    public SecurityOauth2RefreshToken refreshToken() {
        SecurityRequest securityRequest = SecurityFactory.getSecurityRequest.get();
        //刷新token
        String refreshToken = securityRequest.getParameterNonNull(SecurityOauth2ParamConstant.REFRESH_TOKEN);
        SecurityOauth2Repository securityOauth2Repository = SecurityOauth2Manager.getSecurityOauth2Repository();
        String accessToken = securityOauth2Repository.accessTokenByRefreshToken(refreshToken);
        if (StringUtils.isBlank(accessToken)) {
            throw new SecurityOauth2Exception(SecurityOauth2ErrorCodeConstant.OAUTH2_CODE_INVALID_REFRESH_TOKEN, "refreshToken已失效");
        }
        SecurityService securityService = SecurityFactory.getSecurityService.get();
        //获取用户信息
        SecuritySession session = securityService.getSecuritySessionByToken(accessToken);
        if (Objects.isNull(session) || Objects.isNull(session.getCurrentSecurityToken())) {
            throw new SecurityOauth2Exception(ErrorCodeConstant.CODE_INVALID_SESSION, "续约用户信息已失效");
        }
        SecurityOauth2RefreshToken oauth2RefreshToken = new SecurityOauth2RefreshToken();
        //更新续约时间
        session.renewalToken(accessToken);
        oauth2RefreshToken.setAccessToken(accessToken);
        oauth2RefreshToken.setRefreshToken(refreshToken);
        oauth2RefreshToken.setRefreshExpiresIn(securityOauth2Repository.refreshTokenTimeOut(refreshToken));
        return oauth2RefreshToken;
    }

    @Override
    public SecurityOauth2Details getUserInfo(String accessToken) {
        SecurityOauth2Repository securityOauth2Repository = SecurityOauth2Manager.getSecurityOauth2Repository();
        return securityOauth2Repository.getOauth2DetailsByAccessToken(accessToken);
    }

}
